<template>
  <div class="relative w-full h-full">
    <div class="op-wrap">
      <el-upload action="" :before-upload="openBpmn" style="height: 32px; margin-right: 12px">
        <el-tooltip effect="dark" content="加载xml" placement="bottom">
          <el-button icon="FolderOpened" />
        </el-tooltip>
      </el-upload>
      <el-tooltip effect="dark" content="新建" placement="bottom">
        <el-button icon="CirclePlusFilled" @click="newDiagram" />
      </el-tooltip>
      <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
        <el-button icon="Rank" @click="fitViewport" />
      </el-tooltip>
      <el-tooltip effect="dark" content="放大" placement="bottom">
        <el-button icon="ZoomIn" @click="zoomViewport(true)" />
      </el-tooltip>
      <el-tooltip effect="dark" content="缩小" placement="bottom">
        <el-button icon="ZoomOut" @click="zoomViewport(false)" />
      </el-tooltip>
      <el-tooltip effect="dark" content="后退" placement="bottom">
        <el-button icon="Back" @click="modeler.get('commandStack').undo()" />
      </el-tooltip>
      <el-tooltip effect="dark" content="前进" placement="bottom">
        <el-button icon="Right" @click="modeler.get('commandStack').redo()" />
      </el-tooltip>
      <div class="flex-1"></div>
      <el-button v-if="nodeText" icon="View" @click="handleNode">{{ nodeText }}</el-button>
      <el-button icon="View" @click="showXML">查看xml</el-button>
      <el-button icon="Download" @click="saveXML(true)">下载xml</el-button>
      <el-button icon="Picture" @click="saveImg('svg', true)">下载svg</el-button>
      <el-button type="primary" @click="save">保存模型</el-button>
      <el-button v-if="!enable" type="primary" @click="startUp">启用流程</el-button>
      <el-button type="success" icon="ArrowLeft" @click="fallback">返回</el-button>
    </div>
    <div class="flow-containers">
      <div class="canvas" ref="canvasRef"></div>
      <panel
        class="panel"
        v-if="modeler"
        :modeler="modeler"
        :users="users"
        :groups="groups"
        :postList="postList"
        :exps="exps"
        :categorys="categorys"
      />
    </div>
  </div>
</template>

<script setup name="WorkflowBpmnModeler">
  import { ref, watch, onMounted, defineProps, defineEmits } from 'vue';
  import Modeler from 'bpmn-js/lib/Modeler';
  import lintModule from 'bpmn-js-bpmnlint';
  import customTranslate from './common/customTranslate';
  import getInitStr from './flowable/init';
  import customControlsModule from './customPanel';
  import FlowableModule from './flowable/flowable.json';
  import panel from './PropertyPanel';

  const props = defineProps({
    xml: {
      type: String,
      default: '',
    },
    users: {
      type: Array,
      default: () => [],
    },
    groups: {
      type: Array,
      default: () => [],
    },
    postList: {
      type: Array,
      default: () => [],
    },
    categorys: {
      type: Array,
      default: () => [],
    },
    exps: {
      type: Array,
      default: () => [],
    },
    nodeText: {
      type: String,
      default: () => '',
    },
    enable: {
      type: Boolean,
      default: false,
    },
  });

  const emits = defineEmits(['showXML', 'save', 'startUp', 'handleNode', 'fallback']);

  const canvasRef = ref(null);
  const modeler = ref(null);
  const zoom = ref(1);

  watch(
    () => props.xml,
    (val) => {
      if (val) {
        createNewDiagram(val);
      }
    },
  );

  onMounted(() => {
    initModeler();
    if (!props.xml) {
      newDiagram();
    } else {
      createNewDiagram(props.xml);
    }
  });

  function initModeler() {
    modeler.value = new Modeler({
      container: canvasRef.value,
      additionalModules: [
        lintModule,
        customControlsModule,
        {
          //汉化
          translate: ['value', customTranslate],
        },
      ],
      moddleExtensions: {
        flowable: FlowableModule,
      },
    });
  }

  function newDiagram() {
    createNewDiagram(getInitStr());
  }

  async function createNewDiagram(data) {
    data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function (match, str) {
      return str.replace(/</g, '&lt;');
    });
    try {
      await modeler.value.importXML(data);
      fitViewport();
    } catch (err) {
      console.error(err.message, err.warnings);
    }
  }

  function fitViewport() {
    zoom.value = modeler.value.get('canvas').zoom('fit-viewport');
    const bbox = document.querySelector('.flow-containers .viewport').getBBox();
    const currentViewbox = modeler.value.get('canvas').viewbox();
    const elementMid = {
      x: bbox.x + bbox.width / 2 - 65,
      y: bbox.y + bbox.height / 2,
    };
    modeler.value.get('canvas').viewbox({
      x: elementMid.x - currentViewbox.width / 2,
      y: elementMid.y - currentViewbox.height / 2,
      width: currentViewbox.width,
      height: currentViewbox.height,
    });
    zoom.value = (bbox.width / currentViewbox.width) * 1.8;
  }

  function zoomViewport(zoomIn = true) {
    zoom.value = modeler.value.get('canvas').zoom();
    zoom.value += zoomIn ? 0.1 : -0.1;
    modeler.value.get('canvas').zoom(zoom.value);
  }

  function getProcessElement() {
    const rootElements = modeler.value.getDefinitions().rootElements;
    for (let i = 0; i < rootElements.length; i++) {
      if (rootElements[i].$type === 'bpmn:Process') {
        return rootElements[i];
      }
    }
  }

  function getProcess() {
    const element = getProcessElement();
    return {
      id: element.id,
      name: element.name,
      category: element.$attrs['flowable:processCategory'],
      documentation: element.documentation && element.documentation[0].text,
    };
  }

  function downloadFile(filename, data, type) {
    const a = document.createElement('a');
    const url = window.URL.createObjectURL(new Blob([data], { type: type }));
    a.href = url;
    a.download = filename;
    a.click();
    window.URL.revokeObjectURL(url);
  }

  async function saveXML(download = false) {
    try {
      const { xml } = await modeler.value.saveXML({ format: true });
      if (download) {
        downloadFile(`${getProcessElement().name}.bpmn20.xml`, xml, 'application/xml');
      }
      return xml;
    } catch (err) {
      console.log(err);
    }
  }

  async function showXML() {
    try {
      const xml = await saveXML();
      console.log(xml);
      emits('showXML', xml);
    } catch (err) {
      console.log(err);
    }
  }

  // eslint-disable-next-line
  async function saveImg(type = 'svg', download = false) {
    try {
      const { svg } = await modeler.value.saveSVG({ format: true });
      if (download) {
        downloadFile(getProcessElement().name, svg, 'image/svg+xml');
      }
      return svg;
    } catch (err) {
      console.log(err);
    }
  }

  async function save() {
    const process = getProcess();
    const xml = await saveXML();
    const svg = await saveImg();
    const result = { process, xml, svg };
    console.log(result);
    emits('save', result);
    window.parent.postMessage(result, '*');
  }

  function openBpmn(file) {
    const reader = new FileReader();
    reader.readAsText(file, 'utf-8');
    reader.onload = () => {
      createNewDiagram(reader.result);
    };
    return false;
  }

  async function startUp() {
    const process = getProcess();
    const xml = await saveXML();
    const svg = await saveImg();
    const result = { process, xml, svg };
    console.log(result);
    emits('startUp', result);
    window.parent.postMessage(result, '*');
  }

  function fallback() {
    emits('fallback');
  }

  function handleNode() {
    emits('handleNode');
  }

  // eslint-disable-next-line
  async function verifyXML() {
    const linting = modeler.value.get('linting');
    linting.toggle();
  }
</script>

<style lang="scss">
  @import 'bpmn-js/dist/assets/diagram-js.css';
  @import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
  @import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
  @import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';

  .op-wrap {
    display: flex;
    flex-direction: row;
    align-items: center;
    height: 36px;
  }

  .flow-containers {
    position: relative;

    .bjs-powered-by {
      display: none;
    }

    .canvas {
      position: absolute;
      top: 0;
      left: 0;
      width: 1200px;
      height: 800px;
      border: 1px solid grey;
    }

    .panel {
      height: 800px;
      margin-left: 1200px;
    }
  }
</style>
